package controllers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import auxilary.Actions;
import auxilary.Constants;
import auxilary.Links;
import auxilary.Log;
import database.CourseHandler;
import database.SessionHandler;
import entities.Course;
import entities.Session;
import entities.User;

/* ******************************************************** */
/* 						CourseController					*/
/*															*/
/* GET: get requests are for displaying pages				*/
/* 		/Courses - all courses page							*/
/* 		/Courses/new - new course page						*/
/* 		/Courses/{courseId} - course with courseId page		*/
/*															*/
/* POST: post requests are for performing actions		 	*/
/*		 NEW, DELETE, ADD_TO_WISH_LIST,REGISTER,			*/
/*  	 REMOVE_FROM_WISH_LIST, UNREGISTER					*/
/*															*/
/* ******************************************************** */

public class CourseController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public CourseController() {
		super();
	}

	/*
	 * GET - for page display
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// Get request's pathInfo and decide what page to show accordingly
		String pathInfo = request.getPathInfo();
		User user = (User) request.getSession().getAttribute("user");
		if (!canSeePage(user, pathInfo)) {
			response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
			request.getRequestDispatcher("/errors/401.jsp").forward(request,
					response);
			return;
		}
		if ((pathInfo == null) || (pathInfo.equals("/"))) {
			// basic url "/Courses"
			coursesPage(request, response);
			return;
		}
		pathInfo = pathInfo.substring(1);
		if (pathInfo.toLowerCase().equals("new")) {
			// url "/Courses/new"
			newCoursePage(request, response);
		} else if (pathInfo.toLowerCase().equals("search")) {
			// url "/Courses/Search"
			searchPage(request, response);
		} else {
			// url "/Courses/{courseId}
			coursePage(request, response, pathInfo);
		}
	}

	/*
	 * POST - for actions
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		User user = (User) request.getSession().getAttribute("user");
		int action = Integer.parseInt(request.getParameter("action"));
		if (!CanDoAction(user, action)) {
			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
			request.getRequestDispatcher("/errors/400.jsp").forward(request,
					response);
			return;
		}
		String msg = "";
		switch (action) {
			case Actions.Course.NEW :
				Log.dbg("New course");
				msg = newCourse(request, response);
				break;
			case Actions.Course.DELETE :
				Log.dbg("Delete course");
				msg = deleteCourse(request, response);
				break;
			case Actions.Course.ADD_TO_WISHLIST :
			case Actions.Course.REGISTER :
			case Actions.Course.UNREGISTER :
			case Actions.Course.REMOVE_FROM_WISH_LIST :
				Log.dbg("Change course status to user");
				msg = changeStatus(request, response, action);
				break;
			case Actions.Course.SEARCH :
				Log.dbg("Performing Search");
				msg = search(request, response);
				break;
			case Actions.Course.COURSES_VIEW :
				msg = courses(request, response);
				break;
			default :
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				msg = ("Action doesn't exist");
		}
		try {
			response.getWriter().write(msg);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}
		return;
	}

	/* **************************** */
	/* Page callers (called by GET) */
	/* **************************** */

	/*
	 * Course page - specific course page (called by id)
	 */
	private void coursePage(HttpServletRequest request,
			HttpServletResponse response, String idString)
			throws ServletException, IOException {
		// get id from path
		int id = 0;
		try {
			id = Integer.parseInt(idString);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			request.getRequestDispatcher("/errors/400.jsp").forward(request,
					response);
			return;
		}
		CourseHandler ch = new CourseHandler();
		SessionHandler sh = new SessionHandler();

		// get course for page
		Course course = null;
		try {
			course = ch.fetchCourse(id, null);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			request.getRequestDispatcher("/errors/400.jsp").forward(request,
					response);
			return;
		}
		if (course != null) {
			try {
				course.setSessions(sh.fetchAllCourseSessions(course, null));
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				request.getRequestDispatcher("/errors/400.jsp").forward(request,
						response);
			}
		}
		request.setAttribute("course", course);

		// get course status (of user) for page (if no user then status is -1)
		int status = -1;
		User user = (User) request.getSession().getAttribute("user");
		if (user != null) {
			try {
				status = ch.getStatusOfCourseUser(course, user);
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				request.getRequestDispatcher("/errors/400.jsp").forward(request,
						response);
				return;
			}
		}
		request.setAttribute("status", status);

		// check if course clashes
		boolean clash = false;
		if (user != null) {
			try {
				clash = ch.clashes(user, course.getId(), null);
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				request.getRequestDispatcher("/errors/400.jsp").forward(request,
						response);
				return;
			}
		}
		request.setAttribute("clash", clash);

		// call course.jsp page
		request.getRequestDispatcher("/WEB-INF/course.jsp").forward(request,
				response);
	}

	/*
	 * New course page - page for creating new courses
	 */
	private void newCoursePage(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// call new course jsp page
		request.getRequestDispatcher("/WEB-INF/newcourse.jsp").forward(request,
				response);
	}

	/*
	 * Courses page - show all courses page
	 */
	private void coursesPage(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// get all courses for page
		List<Course> courses = getAllCourses();
		request.setAttribute("courses", courses);

		// call courses jsp page
		request.getRequestDispatcher("/WEB-INF/courses.jsp").forward(request,
				response);
	}

	/*
	 * Search page - show all courses page
	 */
	private void searchPage(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// call courses jsp page
		request.getRequestDispatcher("/WEB-INF/search.jsp").forward(request,
				response);
	}

	/* *********************************** */
	/* Performing actions (called by POST) */
	/* *********************************** */

	/*
	 * Add new course
	 */
	private String newCourse(HttpServletRequest request,
			HttpServletResponse response) {
		// get variables from POST and check if they're legal
		User user = (User) request.getSession().getAttribute("user");
		String name = request.getParameter("name");
		String description = request.getParameter("description");
		int credits = 0;
		try {
			credits = Integer.parseInt(request.getParameter("credits"));
		} catch (Exception e) {
			// credits must be int
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Credits must be a number");
		}
		int capacity = 0;
		try {
			capacity = Integer.parseInt(request.getParameter("capacity"));
		} catch (Exception e) {
			// capacity must be int
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Capacity must be a number");
		}
		int groupId = 0;
		try {
			groupId = Integer.parseInt(request.getParameter("group"));
		} catch (Exception e) {
			// groupId must be int
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Please select a group");
		}
		if (groupId == -2) {
			try {
				groupId = Integer
						.parseInt(request.getParameter("group-number"));
				if (groupId < 1) {
					throw new Exception();
				} else
					groupId += Constants.Group.NUM_OF_GROUPS - 1;
			} catch (Exception e) {
				// groupId must be int
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("Please select a group id or select a specific group");
			}
		}
		int numOfSlots = 0;
		try {
			numOfSlots = Integer.parseInt(request.getParameter("numOfSlots"));
		} catch (Exception e) {
			// numOfSlots must be int
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Please select number of slots");
		}
		// get all sessions of course
		List<Session> sessions = new LinkedList<Session>();
		for (int i = 1; i <= numOfSlots; i++) {
			try {
				sessions.add(new Session(-1, Integer.parseInt(request
						.getParameter("start-" + String.valueOf(i))), Integer
						.parseInt(request.getParameter("end-"
								+ String.valueOf(i)))));
			} catch (Exception e) {
				// sessions are illegal
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("You must fill out all session times");
			}
		}
		Course course = new Course(-1, name, description, credits, capacity,
				groupId, user.getId());
		course.setSessions(sessions);
		// ad course to db
		CourseHandler ch = new CourseHandler();
		try {
			int id = ch.addCourse(course);
			if (id<0) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("Sessions collide with other sessions in group");
			}
			course.setId(id);
		} catch (Exception e) {
			// failed to add course
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Course name is taken");
		}
		// course was added successfully
		response.setStatus(HttpServletResponse.SC_OK);
		return (String.valueOf(course.getId()));
	}

	/*
	 * delete course
	 */
	private String deleteCourse(HttpServletRequest request,
			HttpServletResponse response) {
		String name = request.getParameter("name");
		CourseHandler ch = new CourseHandler();
		Course course = null;
		if (name != null) {
			try {
				course = ch.fetchCourse(name);
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("Course doesn't exist");
			}
		} else {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Course doesn't exist");
		}
		try {
			ch.deleteCourse(course.getId());
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Couldn't delete course");
		}
		// Course was deleted
		response.setStatus(HttpServletResponse.SC_OK);
		return ("OK");
	}

	/*
	 * Change status of course for user
	 */
	private String changeStatus(HttpServletRequest request,
			HttpServletResponse response, int action) {
		// get id of course
		int id = Integer.parseInt(request.getParameter("id"));
		// get user
		User user = (User) request.getSession().getAttribute("user");

		// changing status
		CourseHandler ch = new CourseHandler();
		int status = 0;
		switch (action) {
			case Actions.Course.ADD_TO_WISHLIST :
				status = 0;
				break;
			case Actions.Course.REGISTER :
				status = 1;
				break;
			default :
				status = -1;
				break;
		}

		try {
			ch.changeUserCourseStatus(id, user, status);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Couldn't perform, try again later");
		}
		// status has been changes successfully
		response.setStatus(HttpServletResponse.SC_OK);
		return ("OK");
	}

	/*
	 * Add new course
	 */
	private String search(HttpServletRequest request,
			HttpServletResponse response) {
		// get variables from POST and check if they're legal
		String query = request.getParameter("query");
		String creditsString = request.getParameter("credits");
		int credits = -1;
		int sign = -1;
		int group = -1;
		int show = -1;
		if (creditsString != "") {
			try {
				credits = Integer.parseInt(creditsString);
				sign = Integer.parseInt(request.getParameter("sign"));
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("Cresits must be a number");
			}
		}
		try {
			group = Integer.parseInt(request.getParameter("group"));
			show = Integer.parseInt(request.getParameter("show"));
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Bad group selection");
		}
		if (group == -2) {
			String gnumber = request.getParameter("group-number");
			try {
				group = Integer.parseInt(gnumber)
						+ Constants.Group.NUM_OF_GROUPS - 1;
			} catch (Exception e) {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				return ("Bad group selection");
			}
		}
		if (show == -1) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("You fill out all fields");
		}

		// handle tags uniqueness
		String[] tags = query.toLowerCase().split(" ");
		if (tags.length == 0) {
			// if no tags create empty tags so all courses get returned
			tags = new String[1];
			tags[0] = "";
		}
		String lastTag = tags[tags.length - 1];
		String[] uniqueTags = null;
		int leaveOut = 1;
		if (query.endsWith(" ")) {
			lastTag = "";
			leaveOut = 0;
		}
		if (tags.length > leaveOut) {
			Set<String> set = new HashSet<String>();
			for (int i = 0; i < tags.length - leaveOut; i++) {
				if ((!tags[i].equals(" ")) && (!tags[i].equals(""))) {
					set.add(tags[i]);
				}
			}
			if (!set.isEmpty()) {
				uniqueTags = Arrays.copyOf(set.toArray(), set.toArray().length,
						String[].class);
			}
		}
		boolean withFull = true;
		boolean onlyAvailable = false;
		switch (show) {
			case 2 :
				onlyAvailable = true;
			case 4 :
				withFull = false;
				break;
			case 3 :
				onlyAvailable = true;
			default :
				withFull = true;
				break;
		}

		// get courses by tags
		CourseHandler ch = new CourseHandler();
		List<Course> courses = new LinkedList<Course>();
		User user = (User) request.getSession().getAttribute("user");
		try {
			courses = ch.fetchCoursesByTags(uniqueTags, lastTag, credits, sign,
					group, withFull, onlyAvailable, user);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("No courses exist");
		}

		// create response
		String result = getHtmlForSearchPage(query, credits, sign, group,
				courses);
		response.setStatus(HttpServletResponse.SC_OK);
		return result;
	}

	/*
	 * Add new course
	 */
	private String courses(HttpServletRequest request,
			HttpServletResponse response) {
		// get variables from POST and check if they're legal
		int type = -1;
		try {
			type = Integer.parseInt(request.getParameter("type"));
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return ("Type must be a number");
		}
		// get courses by type
		CourseHandler ch = new CourseHandler();
		List<Course> courses = new LinkedList<Course>();
		User user = (User) request.getSession().getAttribute("user");
		try {
			switch (type) {
				case Actions.Course.Type.ALL :
					courses = ch.fetchAllCourses();
					break;
				case Actions.Course.Type.NOT_FULL :
					courses = ch.fetchAllNotFullCourses();
					break;
				case Actions.Course.Type.AVAILABLE :
					List<Course> optionalCourses = new LinkedList<Course>();
					optionalCourses = ch.fetchAllNotFullCourses();
					for (Course course : optionalCourses) {
						try {
							if (!ch.clashes(user, course.getId(), null)) {
								courses.add(course);
							}
						} catch (Exception e) {
							Log.err("trouble checking clashing of course");
						}
					}

					break;
				case Actions.Course.Type.MY_COURSES :
					courses = ch.fetchAllCourses(user.getId());
					break;
			}
		} catch (Exception e) {
			courses = new LinkedList<Course>();
		}
		// create response
		String result = getHtmlForCoursesPage(courses, user);
		response.setStatus(HttpServletResponse.SC_OK);
		return result;
	}
	/* **************** */
	/* Helper functions */
	/* **************** */

	/*
	 * Get Html for Search Page - get table or no result message
	 */
	private String getHtmlForSearchPage(String query, int credits, int sign,
			int group, List<Course> courses) {
		String result = "";
		if (courses.size() > 0) {
			result += "<table><tr><th class='course-name'>Name</th>"
					+ "<th class='course-points'>Points</th>"
					+ "<th class='course-availabe'>Spots</th><th class='course-group'>Group</th></tr>";
			for (Course course : courses) {
				result += "<tr id='"
						+ course.getId()
						+ "'>"
						+ "<td class='course-name'>"
						+ Constants.truncate(course.getName(), 20)
						+ "</td><td class='course-points'>"
						+ course.getCredits()
						+ "</td><td class='course-points'>"
						+ course.getTaken()
						+ "/"
						+ course.getCapacity()
						+ "</td><td class='course-group'>"
						+ Constants.Group.getName(course.getGroupId())
						+ "</td><td class='course-view' onclick='window.location.href=\""
						+ Links.Pages.COURSES_BASE
						+ String.valueOf(course.getId()) + "\"'>view</td>"
						+ "</tr>";
			}
			result += "</table>";
		} else {
			result = "<div class='no-match'>No courses match for \"" + query
					+ "\"";
			if (credits != -1) {
				result += "<br /><br />number of credits ";
				switch (sign) {
					case 0 :
						result += "equals ";
						break;

					case 1 :
						result += "are smaller or equal ";
						break;

					case 2 :
						result += "are bigger or equal ";
						break;

					default :
						break;
				}
				result += String.valueOf(credits);
			}
			if (group != -1) {
				result += "<br /><br />In group "
						+ Constants.Group.getName(group);
			}
			result += "</div>";
		}
		return result;
	}

	/*
	 * Get html for courses page
	 */
	private String getHtmlForCoursesPage(List<Course> courses, User user) {
		String result = "";
		if ((courses != null) && (courses.size() > 0)) {
			result += "<table><tr><th class='course-name'>Name</th>"
					+ "<th class='course-points'>Points</th>"
					+ "<th class='course-availabe'>Spots</th><th class='course-group'>Group</th></tr>";
			for (Course course : courses) {
				result += "<tr id='"
						+ course.getId()
						+ "'>"
						+ "<td class='course-name'>"
						+ Constants.truncate(course.getName(), 20)
						+ "</td><td class='course-points'>"
						+ course.getCredits()
						+ "</td><td class='course-points'>"
						+ course.getTaken()
						+ "/"
						+ course.getCapacity()
						+ "</td><td class='course-group'>"
						+ Constants.Group.getName(course.getGroupId())
						+ "</td><td class='course-view' onclick='window.location.href=\""
						+ Links.Pages.COURSES_BASE
						+ String.valueOf(course.getId()) + "\"'>view</td>";
				if ((user != null) && (user.isAdmin())
						&& (user.getId() == course.getCreator())) {
					result += "<td class='x' onclick='deleteCourseByName("
							+ Actions.Course.DELETE + ",\"" + course.getName()
							+ "\")''><img src='" + Links.PROJECT_BASE
							+ "images/x.png' /></td>";
				}
				result += "</tr>";
			}
			result += "</table>";
		} else {
			result = "<br /><br /><center><h3>There are no courses</h3></center>";
		}
		return result;
	}

	/*
	 * Get all courses
	 */
	private List<Course> getAllCourses() {
		CourseHandler ch = new CourseHandler();
		List<Course> courses = null;
		try {
			courses = ch.fetchAllCourses();
		} catch (Exception e) {
			Log.err("Courses[getAllCourses]: exception");
		}
		if (courses == null) {
			courses = new ArrayList<Course>();
		}
		return courses;
	}


	/*
	 * action filter
	 */
	private boolean CanDoAction(User user, int action) {
		if (user == null) {
			// unregistered user
			switch (action) {
				case Actions.Course.SEARCH :
				case Actions.Course.COURSES_VIEW :
					return true;
				default :
					return false;
			}
		} else if (!user.isAdmin()) {
			// registered user NOT ADMIN
			switch (action) {
				case Actions.Course.SEARCH :
				case Actions.Course.ADD_TO_WISHLIST :
				case Actions.Course.REGISTER :
				case Actions.Course.UNREGISTER :
				case Actions.Course.REMOVE_FROM_WISH_LIST :
				case Actions.Course.COURSES_VIEW :
					return true;
				default :
					return false;
			}
		} else if (user.isAdmin()) {
			// admin
			return true;
		}
		return false;
	}

	/*
	 * page filter
	 */
	private boolean canSeePage(User user, String pathInfo) {
		if ((user == null) || (!user.isAdmin())) {
			// unregistered user or registered user NOT ADMIN
			if ((pathInfo != null) && (pathInfo.toLowerCase().equals("/new"))) {
				// url "/Courses/new"
				return false;
			}
			return true;
		}
		// admin
		return true;
	}
}
